﻿using Orleans;
using Puppy.Actor;
using Puppy.Domain.Entities;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Puppy.ActorDb
{
    public class ActorSet<TEntity> : Grain<ActorSetState>, IActorSet<TEntity>
        where TEntity : class, IEntity
    {
        public override Task OnActivateAsync()
        {
            if (State.Keys == null)
            {
                State.Keys = new HashSet<string>();
            }

            return base.OnActivateAsync();
        }

        public async Task AddOrUpdateAsync(TEntity entity)
        {
            if (!State.Keys.Any(p => p == GetEntityKey(entity)))
            {
                State.Keys.Add(GetEntityKey(entity));
            }

            var actor = GetActor(entity);

            await actor.WriteEntityAsync(entity);

            await WriteStateAsync();
        }

        public async Task AddOrUpdateRangeAsync(params TEntity[] entities)
        {
            foreach (var entity in entities)
            {
                await AddOrUpdateAsync(entity);
            }
        }

        public async Task AddOrUpdateRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default)
        {
            await AddOrUpdateRangeAsync(entities.ToArray());
        }

        public async Task RemoveAsync(TEntity entity)
        {
            State.Keys.Remove(GetEntityKey(entity));

            var actor = GetActor(entity);

            await actor.ClearEntityAsync();

            await WriteStateAsync();
        }

        public async Task RemoveRangeAsync(params TEntity[] entities)
        {
            foreach (var entity in entities)
            {
                await RemoveAsync(entity);
            }
        }

        public async Task RemoveRangeAsync(IEnumerable<TEntity> entities)
        {
            await RemoveRangeAsync(entities.ToArray());
        }

        public Task<List<string>> GetAllKeyAsync()
        {
            return Task.FromResult(State.Keys.ToList());
        }

        public Task<TEntity> FindAsync(object[] keyValues, CancellationToken cancellationToken = default)
        {
            if (keyValues == null || keyValues.Any(p => p == null))
            {
                return default;
            }

            var key = keyValues.JoinAsString(",");
            if (State.Keys.Any(p => p == key))
            {
                return default;
            }

            return GetEntity(key);
        }

        public Task<long> CountAsync()
        {
            return Task.FromResult(State.Keys.LongCount());
        }

        private static string GetEntityKey(TEntity entity)
        {
            return entity.GetKeys().JoinAsString(",");
        }

        private IActor<TEntity> GetActor(TEntity entity)
        {
            return GetActor(GetEntityKey(entity));
        }

        private IActor<TEntity> GetActor(string key)
        {
            return GrainFactory.GetGrain<IActor<TEntity>>(key);
        }

        private Task<TEntity> GetEntity(string key)
        {
            var actor = GetActor(key);
            return actor.ReadEntityAsync();
        }
    }

    public class ActorSetState
    {
        public HashSet<string> Keys { get; set; }
    }
}
